_package("alz.util");

/**
 * @class alz.util.HashMap
 * @extends alz.util.Observable
 * A simple unoredered dictionary implementation to store key/value pairs.
 *
 * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
 * A default is provided that returns the <b>id</b> property on the object. This function is only used
 * if the add method is called with a single argument.
 *
 * @constructor
 * @param {Object} config The configuration options
 */
_class("HashMap", "", function(){
//_class("HashMap", Observable, function(){
	this.events = {};
	this.addEvents = function(o){
		//console.log("HashMap::addEvents");
		var a = arguments;
		for(var i = 0, len = a.length; i < len; i++){
			var v = a[i];
			this.events[v] = this.events[v] || true;
		}
	};
	this.fireEvent = function(type, map, key, value, old){
		//console.log("HashMap::fireEvent");
		var a = [];
		for(var i = 0, len = arguments.length; i < len; i++){
			a.push(arguments[i]);
		}
		var type = type.toLowerCase();
		var ret = true;
		var ev = this.events[type];  //true|false
		/*
		if(ev && typeof ev == "object"){
			if(ev.bubble){
				if(ev.fire.apply(ev, a.slice(1)) === false){
					return false;
				}
				var parent = this.getBubbleTarget && this.getBubbleTarget();
				if(parent && parent.isObservable){
					if(!parent.events[type] || !Ext.isObject(parent.events[type]) || !parent.events[type].bubble){
						parent.enableBubble(type);
					}
					return parent.fireEvent.apply(parent, a);
				}
			}else{
				a.shift();
				ret = ev.fire.apply(ev, a);
			}
		}
		*/
		return ret;
	};
	this._init = function(config){
		this.addEvents(
			'add'   ,  //(type, hashmap, key, value) Fires when a new item is added to the hash
			'clear' ,  //(type, hashmap, key, value) Fires when the hash is cleared.
			'remove',  //(type, hashmap, key, value) Fires when an item is removed from the hash.
			'replace'  //(type, hashmap, key, value, old) Fires when an item is replaced in the hash.
		);
		_super._init.call(this, config);
		this.length = 0;
		this.map = {};
	};
	this.dispose = function(){
		for(var k in this.map){
			this.map[k].dispose();
			delete this.map[k];
		}
		this.length = 0;
		_super.dispose.apply(this);
	};
	/* *
	 * Gets the number of items in the hash.
	 * @return {Number} The number of items in the hash.
	 */
	/*
	this.getCount = function(){
		return this.length;
	};
	*/
	/**
	 * Implementation for being able to extract the key from an object if only
	 * a single argument is passed.
	 * @private
	 * @param {String} key The key
	 * @param {Object} value The value
	 * @return {Array} [key, value]
	 */
	this.getData = function(key, value){
		// if we have no value, it means we need to get the key from the object
		if(typeof value == "undefined"){
			value = key;
			key = this.getKey(value);
		}
		return [key, value];
	};
	/**
	 * Extracts the key from an object. This is a default implementation, it may be overridden
	 * @private
	 * @param {Object} o The object to get the key from
	 * @return {String} The key to use.
	 */
	this.getKey = function(o){
		return o.id;
	};
	/**
	 * Add a new item to the hash. An exception will be thrown if the key already exists.
	 * @param {String} key The key of the new item.
	 * @param {Object} value The value of the new item.
	 * @return {Object} The value of the new item added.
	 */
	this.add = function(key, value){
		if(this.containsKey(key)){
			throw 'This key already exists in the HashMap';
		}
		var data = this.getData(key, value);
		key = data[0];
		value = data[1];
		this.map[key] = value;
		++this.length;
		this.fireEvent('add', this, key, value);
		return value;
	};
	/* *
	 * Replaces an item in the hash. If the key doesn't exist, the
	 * {@link #add} method will be used.
	 * @param {String} key The key of the item.
	 * @param {Object} value The new value for the item.
	 * @return {Object} The new value of the item.
	 */
	/*
	this.replace = function(key, value){
		var map = this.map, old;
		if(!this.containsKey(key)){
			this.add(key, value);
		}
		old = map[key];
		map[key] = value;
		this.fireEvent('replace', this, key, value, old);
		return value;
	};
	*/
	/**
	 * Remove an item from the hash.
	 * @param {Object} o The value of the item to remove.
	 * @return {Boolean} True if the item was successfully removed.
	 */
	this.remove = function(o){
		var key = this.findKey(o);
		if(key !== undefined){
			return this.removeByKey(key);
		}
		return false;
	};
	/**
	 * Remove an item from the hash.
	 * @param {String} key The key to remove.
	 * @return {Boolean} True if the item was successfully removed.
	 */
	this.removeByKey = function(key){
		var value;
		if(this.containsKey(key)){
			value = this.map[key];
			delete this.map[key];
			this.length--;
			this.fireEvent('remove', this, key, value);
			return true;
		}
		return false;
	};
	/**
	 * Retrieves an item with a particular key.
	 * @param {String} key The key to lookup.
	 * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
	 */
	this.get = function(key){
		return this.map[key];
	};
	/**
	 * Removes all items from the hash.
	 * @return {HashMap} this
	 */
	this.clear = function(){
		for(var k in this.map){
			this.map[k].dispose();
			delete this.map[k];
		}
		this.length = 0;
		this.fireEvent('clear', this);
		return this;
	};
	/**
	 * Checks whether a key exists in the hash.
	 * @param {String} key The key to check for.
	 * @return {Boolean} True if they key exists in the hash.
	 */
	this.containsKey = function(key){
		return key in this.map;
	};
	/* *
	 * Checks whether a value exists in the hash.
	 * @param {Object} value The value to check for.
	 * @return {Boolean} True if the value exists in the dictionary.
	 */
	/*
	this.contains = function(value){
		return this.containsKey(this.findKey(value));
	};
	*/
	/* *
	 * Return all of the keys in the hash.
	 * @return {Array} An array of keys.
	 */
	/*
	this.getKeys = function(){
		return this.getArray(true);
	};
	*/
	/* *
	 * Return all of the values in the hash.
	 * @return {Array} An array of values.
	 */
	/*
	this.getValues = function(){
		return this.getArray(false);
	};
	*/
	/* *
	 * Gets either the keys/values in an array from the hash.
	 * @private
	 * @param {Boolean} isKey True to extract the keys, otherwise, the value
	 * @return {Array} An array of either keys/values from the hash.
	 */
	/*
	this.getArray = function(isKey){
		var arr = [], map = this.map;
		for(var key in map){
			if(map.hasOwnProperty(key)){
				arr.push(isKey ? key : map[key]);
			}
		}
		return arr;
	};
	*/
	/* *
	 * Executes the specified function once for each item in the hash.
	 * Returning false from the function will cease iteration.
	 *
	 * The paramaters passed to the function are:
	 * <div class="mdetail-params"><ul>
	 * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
	 * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
	 * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
	 * </ul></div>
	 * @param {Function} fn The function to execute.
	 * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
	 * @return {HashMap} this
	 */
	/*
	this.each = function(fn, scope){
		scope = scope || this;
		// copy items so they may be removed during iteration.
		var items = {};
		for(var k in this.map){
			items[k] = this.map[k];
		}
		var len = this.length;
		for(var key in items){
			if(items.hasOwnProperty(key)){
				if(fn.call(scope, key, items[key], len) === false){
					break;
				}
			}
		}
		return this;
	};
	*/
	/* *
	 * Performs a shallow copy on this hash.
	 * @return {HashMap} The new hash object.
	 */
	/*
	this.clone = function(){
		var hash = new HashMap(),
			map = this.map;
		hash.suspendEvents();
		for(var key in map){
			if(map.hasOwnProperty(key)){
				hash.add(key, map[key]);
			}
		}
		hash.resumeEvents();
		return hash;
	};
	*/
	/**
	 * @private
	 * Find the key for a value.
	 * @param {Object} value The value to find.
	 * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
	 */
	this.findKey = function(value){
		var map = this.map;
		for(var key in map){
			if(map.hasOwnProperty(key) && map[key] === value){
				return key;
			}
		}
		return null;
	};
});